package models;

import com.mysql.jdbc.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import system.DbConnection;
import system.Model;

/**
 * Model Pesanan
 * 
 * @author Rezan Achmad
 */
public class Pesanan extends Model {

    public static final int MIN_STATUS_VALUE = 0;
    public static final int MAX_STATUS_VALUE = 4;
    public static final int STATUS_INCART = 0;
    public static final int STATUS_NEW = 1;
    public static final int STATUS_CONFIRM_PAY = 2;
    public static final int STATUS_PAY = 3;
    public static final int STATUS_ARRIVED = 4;
    public static final int EXPIRED_TIME = 2 * 24 * 3600 * 1000;
    
    public int id;
    public int user_id;
    public String tanggal;
    public int status;
    public int diskon;
    public int kirim;
    public String alamat_kirim;
    public int shipping_service_id;
    public String billno; 
    
    // For JSON
    private ArrayList<PesananItem> pesananItems;

    public Pesanan() {
        diskon = 0;
        kirim = 0;
    }

    /**
     * Menambah pesanan.
     * @return Mengembalikan true jika berhasil atau false jika gagal.
     */
    @Override
    public boolean add() {
        // Set Tanggal
        tanggal = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(Calendar.getInstance().getTime());

        // Build SQL
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO `pesanan` VALUES('")
                .append(id).append("','")
                .append(user_id).append("','")
                .append(tanggal).append("','")
                .append(status).append("','")
                .append(diskon).append("','")
                .append(kirim).append("','")
                .append(alamat_kirim).append("','")
                .append(shipping_service_id).append("','")
                .append(billno).append("')");

        Statement statement = getStatement();

        try {
            // Insert
            statement.executeUpdate(sql.toString(), Statement.RETURN_GENERATED_KEYS);

            // Set Id
            ResultSet result = statement.getGeneratedKeys();
            if (result.next()) {
                id = result.getInt(1);
            }

            // Log
            Log.add(sql.toString());
        } catch (SQLException ex) {
            // Something error
            Logger.getLogger(Pesanan.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }

        return true;
    }

    /**
     * Menambah pesanan beserta dengan itemnya.
     * @param items Item pesanan.
     * @return Mengembalikan true jika berhasil atau false jika gagal.
     */
    public boolean add(ArrayList<PesananItem> items) {
        return add((PesananItem[]) items.toArray());
    }

    /**
     * Menambah pesanan beserta dengan itemnya.
     * @param items Item pesanan.
     * @return Mengembalikan true jika berhasil atau false jika gagal.
     */
    public boolean add(PesananItem[] items) {
        // Periksa ketersedian
        String error = checkAvailability(items);
        if (!error.equals("")) {
            addErrorGlobal(error);
            return false;
        }

        // TODO Rollback

        // Add Pesanan
        if (!add()) {
            return false;
        }

        // Add Pesanan Item
        for (PesananItem item : items) {
            item.pesanan_id = this.id;
            if (!item.add()) {
                return false;
            }
        }

        return true;
    }

    /**
     * Mengambil daftar pesanan berdasarkan query SQL.
     * @param sql Query SQL.
     * @return Mengembalikan Array List Pesanan jika ditemukan atau null jika tidak ditemukan.
     */
    public ArrayList<Pesanan> getAllBySql(String sql) {
        try {
            // Execute Query
            ResultSet rs = DbConnection.getStatement().executeQuery(sql);

            // Check result
            if (!rs.next()) {
                return null;
            }

            // Iterate and add to list
            Pesanan pesanan;
            ArrayList<Pesanan> listPesanan = new ArrayList<Pesanan>();
            do {
                pesanan = new Pesanan();
                pesanan.setAttributes(rs);
                listPesanan.add(pesanan);
            } while (rs.next());

            return listPesanan;
        } catch (SQLException ex) {
            Logger.getLogger(Pesanan.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    /**
     * Mengambil pesanan berdasarkan id pesanan.
     * @param pesananId Id Pesanan.
     * @return Mengembalikan objek Pesanan jika ditemukan atau null jika tidak ditemukan.
     */
    public Pesanan get(int pesananId) {
        // Build Sql
        String sql = "SELECT * FROM `pesanan` WHERE `id` = '" + pesananId + "'";

        // Get By Sql
        ArrayList<Pesanan> result = getAllBySql(sql);

        if (result != null) {
            return result.get(0);
        } else {
            return null;
        }
    }

    public ArrayList<Pesanan> getAll() {
        String sql = "SELECT * FROM `pesanan`";
        return getAllBySql(sql);
    }

    public ArrayList<Pesanan> getAll(int userId) {
        String sql = "SELECT * FROM `pesanan` WHERE `user_id` = '" + userId + "'";
        return getAllBySql(sql);
    }

    public ArrayList<Pesanan> getAll(int userId, int status) {
        String sql = "SELECT * FROM `pesanan` WHERE `user_id` = '" + userId + "' AND `status`='" + status + "'";
        return getAllBySql(sql);
    }

    public ArrayList<Pesanan> getAllByStatus(int status) {
        String sql = "SELECT * FROM `pesanan` WHERE `status`='" + status + "'";
        return getAllBySql(sql);
    }

    /**
     * Mengambil pesanan yang berstatus baru atau telah dikonfirmasi pembayarannya.
     * 
     * @param userId Id User
     * @return Mengembalikan list pesanan jika ditemukan, null jika tidak.
     */
    public ArrayList<Pesanan> getAllPesananMenunggu(int userId) {
        String sql = "SELECT * FROM `pesanan` WHERE (`status` = '" + Pesanan.STATUS_NEW + "' OR `status` = '" + Pesanan.STATUS_CONFIRM_PAY + "') AND `user_id`='" + userId + "'";
        return getAllBySql(sql);
    }
    
    /**
     * Mengambil pesanan yang berstatus baru atau telah dikonfirmasi pembayarannya.
     * 
     * @return Mengembalikan list pesanan jika ditemukan, null jika tidak.
     */
    public ArrayList<Pesanan> getAllPesananMenunggu() {
        String sql = "SELECT * FROM `pesanan` WHERE `status` = '" + Pesanan.STATUS_NEW + "' OR `status` = '" + Pesanan.STATUS_CONFIRM_PAY + "'";
        return getAllBySql(sql);
    }

    /**
     * Mengambil pesanan yang berstatus telah dibayar atau telah tiba.
     * 
     * @param userId Id User
     * @return Mengembalikan list pesanan jika ditemukan, null jika tidak.
     */
    public ArrayList<Pesanan> getAllHistory(int userId) {
        String sql = "SELECT * FROM `pesanan` WHERE (`status` = '" + Pesanan.STATUS_PAY + "' OR `status` = '" + Pesanan.STATUS_ARRIVED + "') AND `user_id`='" + userId + "'";
        return getAllBySql(sql);
    }

    public ArrayList<Pesanan> getAllWithItems() {
        // Get All by userId
        ArrayList<Pesanan> pesanans = getAll();

        // Set each PesananItems
        setPesananItems(pesanans);

        return pesanans;
    }

    public ArrayList<Pesanan> getAllWithItems(int userId) {
        // Get All by userId
        ArrayList<Pesanan> pesanans = getAll(userId);

        // Set each PesananItems
        setPesananItems(pesanans);

        return pesanans;
    }

    public ArrayList<Pesanan> getAllWithItems(int userId, int status) {
        // Get All by userId
        ArrayList<Pesanan> pesanans = getAll(userId, status);

        // Set each PesananItems
        setPesananItems(pesanans);

        return pesanans;
    }

    /**
     * Mengambil seluruh PesananItem yang dimiliki.
     * @return Kumpulan objek PesananItem jika ditemukan atau null jika tidak ditemukan.
     */
    public ArrayList<PesananItem> getPesananItems() {
        return pesananItems = PesananItem.getInstance().getAllByPesananId(id);
    }

    public User getUser() {
        return User.getInstance().get(user_id);
    }

    /**
     * Update Pesanan.
     * @return Mengembalikan true jika berhasil atau false jika gagal.
     */
    public boolean update() {
        // Build SQL
        StringBuilder sql = new StringBuilder();
        sql.append("UPDATE `pesanan` SET ")
                .append("user_id='").append(user_id).append("', ")
                .append("tanggal='").append(tanggal).append("', ")
                .append("status='").append(status).append("', ")
                .append("diskon='").append(diskon).append("', ")
                .append("kirim='").append(kirim).append("', ")
                .append("alamat_kirim='").append(alamat_kirim).append("', ")
                .append("shipping_service_id='").append(shipping_service_id).append("', ")
                .append("billno='").append(billno).append("' ")
                .append("WHERE id='").append(id).append("'");

        try {
            // Update
            getStatement().executeUpdate(sql.toString());

            // Log
            Log.add(sql.toString());
        } catch (SQLException ex) {
            // Something error
            Logger.getLogger(Pesanan.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }

        return true;
    }

    /**
     * Konfirmasi save cart. Nilai atribut <code>status</code> akan menjadi 1.
     * @return Mengembalikan <code>true</code> jika berhasil atau <code>false</code> jika gagal.
     */
    public boolean saveCart() {
        status = Pesanan.STATUS_NEW;
        return update();
    }

    /**
     * Konfirmasi pembayaran pesanan. Nilai atribut <code>status</code> akan menjadi 2.
     * @return Mengembalikan <code>true</code> jika berhasil atau <code>false</code> jika gagal.
     */
    public boolean confirmPay() {
        status = Pesanan.STATUS_CONFIRM_PAY;
        return update();
    }

    public boolean delete() {
        // Get items
        ArrayList<PesananItem> items = getPesananItems();

        // Delete all item
        if (items != null) {
            for (PesananItem item : items) {
                item.delete();
            }
        }

        // Build SQL
        StringBuilder sql = new StringBuilder();
        sql.append("DELETE FROM `pesanan` WHERE `id`='").append(id).append("'");

        try {
            // Delete
            getStatement().executeUpdate(sql.toString());

            // Kembalikan poin
            if (diskon > 0) {
                User.getInstance().batalTukarPoin(user_id);
            }

            // Log
            Log.add(sql.toString());
        } catch (SQLException ex) {
            // Something error
            Logger.getLogger(Pesanan.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }

        return true;
    }

    /**
     * Lakukan pembayaran pada pesanan ini.
     * @return Mengembalikan <code>true</code> jika berhasil atau <code>false</code> jika gagal.
     */
    public boolean pay() {
        status = Pesanan.STATUS_PAY;
        return update();
    }

    /**
     * Menandai jika barang telah tiba.
     * @return Mengembalikan <code>true</code> jika berhasil atau <code>false</code> jika gagal.
     */
    public boolean arrive() {
        status = Pesanan.STATUS_ARRIVED;
        return update();
    }

    /**
     * @return Mengembalikan instance dari kelas <code>Pesanan</code>.
     */
    public static Pesanan getInstance() {
        return new Pesanan();
    }

    /**
     * Memeriksa ketersedian item pesanan.
     * @param items Item pesanan.
     * @return Mengembalikan string kosong jika semua item pesanan available atau 
     * mengembalikan pesan error jika ada item pesanan yang tidak available.
     */
    public String checkAvailability(PesananItem items[]) {
        StringBuilder error = new StringBuilder();

        Barang barang = new Barang();
        for (PesananItem item : items) {
            barang = Barang.get(item.barang_id);
            if (barang == null) {
                error.append("Barang dengan id ").append(item.barang_id).append(" tidak tersedia. ");
            } else if (barang.jumlah < item.jumlah) {
                error.append(barang.nama).append(" hanya berjumlah ").append(barang.jumlah).append(" buah. ");
            }
        }

        return error.toString();
    }

    /**
     * Set semua attribut <code>Pesanan</code>
     * @param rs Result Set
     * @throws SQLException 
     */
    @Override
    public void setAttributes(ResultSet rs) throws SQLException {
        id = rs.getInt("pesanan.id");
        user_id = rs.getInt("pesanan.user_id");
        tanggal = rs.getString("pesanan.tanggal");
        status = rs.getInt("pesanan.status");
        diskon = rs.getInt("pesanan.diskon");
        kirim = rs.getInt("pesanan.kirim");
        alamat_kirim = rs.getString("pesanan.alamat_kirim");
        shipping_service_id = rs.getInt("pesanan.shipping_service_id");
        billno = rs.getString("pesanan.billno");
    }

    public void setPesananItems(ArrayList<Pesanan> pesanans) {
        if (pesanans != null) {
            for (Pesanan p : pesanans) {
                p.getPesananItems();
            }
        }
    }

    /**
     * Mengganti status pesanan. Jika nilai <code>status</code> tidak valid, maka status 
     * sekarang tidak akan diubah.
     * @param status Nilai status.
     */
    public void setStatus(int status) {
        if (Pesanan.isStatusValid(status)) {
            this.status = status;
        }
    }

    public static boolean isStatusValid(int status) {
        return (status >= Pesanan.MIN_STATUS_VALUE && status <= Pesanan.MAX_STATUS_VALUE);
    }

    /**
     * Mendapatkan pesanan terakhir yang aktif (masih bisa ditambah) berdasarkan
     * ID pemesan
     * @param pemesanID ID Pemesan
     * @return Mengembalikan pesanan yang didapat
     */
    public static Pesanan getLastActivePesanan(int pemesanID) {
        Pesanan retVal = new Pesanan();
        ArrayList<Pesanan> listPesananNew = retVal.getAll(pemesanID, Pesanan.STATUS_INCART);
        if (listPesananNew != null && listPesananNew.size() > 0) {
            retVal = listPesananNew.get(0);
            for (Pesanan pesanan : listPesananNew) {
                if (pesanan.id > retVal.id) {
                    retVal = pesanan;
                }
            }
        }
        return retVal;
    }

    @Override
    public boolean validate() {
        return true;
    }

    @Override
    public void initAttributesLabel() {
    }

    public static Map getBySql(String sql) {
        try {
            // Execute Query
            ResultSet rs = DbConnection.getStatement().executeQuery(sql);

            // Check result
            if (!rs.next()) {
                return null;
            }

            // Iterate and add to list
            Map listPesanan = new HashMap();
            do {
                if (listPesanan.containsKey(rs.getString("nama"))) {
                    listPesanan.put(rs.getString("nama"), (Integer) listPesanan.get(rs.getString("nama")) + rs.getInt("jumlah"));
                } else {
                    listPesanan.put(rs.getString("nama"), rs.getInt("jumlah"));
                }
            } while (rs.next());

            return listPesanan;
        } catch (SQLException ex) {
            Logger.getLogger(Pesanan.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * Menambahkan poin ke user sesuai pesanan
     */
    public void addPoin() {
        int point = 0;
        User us = User.getInstance().get(this.user_id);
        ArrayList<PesananItem> pi = getPesananItems();
        Barang barang = null;
        PesananItem item = null;
        for (int i = 0; i < pi.size(); ++i) {
            item = pi.get(i);
            barang = Barang.get(item.barang_id);
            point += item.jumlah * barang.poin;
        }
        us.point += point;
        us.update();
    }

    /**
     * Mengurangi poin ke user sesuai pesanan
     */
    public void subPoin() {
        int point = 0;
        User us = User.getInstance().get(this.user_id);
        ArrayList<PesananItem> pi = getPesananItems();
        Barang barang = null;
        PesananItem item = null;
        for (int i = 0; i < pi.size(); ++i) {
            item = pi.get(i);
            barang = Barang.get(item.barang_id);
            point += item.jumlah * barang.poin;
        }
        us.point -= point;
        us.update();
    }
}
